消息队列常见优化策略总结
《消息队列设计精要》
原文链接:https://tech.meituan.com/mq-design.html
一、消息队列的应用点
- 业务解耦:对于非核心的事务,做到“通知”,而非全部处理“完成”。
最终一致性:
- 强一致性:分布式事务(落地难成本高)
- 最终一致性:”记录“和”补偿“。记录不确定事件,到达成功状态后再清理。可以依靠本地定时任务轮询不断重发。
- 最终一致性不是消息队列必须的
广播:只需关心消息是否送达队列,其他事情交给下游的订阅者
- 错峰流量控制:平衡前后端处理能力的巨大差异
二、消息队列的设计
RPC通信协议:
- 本质:两次或者三次RPC加中间broker的转储
- 尽量选择现有框架;尽量选择本机房投递
高可用:
- 目标:保证broker接受和确认消息是幂等的
- 方式:共享存储,多机器共享db
服务端承载消息堆积:
- 持久化
- 非持久化
- 存储方式:内存,分布式KV,数据库,磁盘
存储子系统选择:可靠性要求高则选择DB
- 消费关系解析:点对点和广播
可靠投递:
- 最终一致性保证导致消息可能重复,异常情况下可能延迟。
重复消息怎么解决:
- 鉴别重复(版本号)
- 幂等处理
- 尽量减少重复消息投递
顺序消息怎么实现:
- 版本号(保存各个消息到来的顺序,重组)
- 状态机
总结: 保证不丢失消息的情况下尽量减少重复消息,消费顺序交给消费者保证
异步和同步
- 服务端异步:解放I/O
- 返回future的方式不一定只有线程池:NIO、事件123456789101112131415161718//客户端异步,服务端异步Future<Result> future = request(server);return future;//客户端异步,服务端同步(开线程池)Future<Result> future = executor.submit(new Callable(){public void call<Result>(){result = request(server);}})return future;//客户端同步,服务端异步Future<Result> future = request(server);synchronized(future){while(!future.isDone()){future.wait();}}return future.get();//客户端同步,服务端同步Result result = request(server);
网络请求小包合并成大包会提高性能
- 减少无谓的请求头
- 减少回复的ACK数量
三、Pull和Push
- Push的缺点:慢消费
- Pull的缺点:消息延迟与忙碌等待
- Pull对于顺序消息的实现容易很多:
- producer对应partition,单线程
- consumer对应partition,消费确认后继续